package com.example.facedetectionopencv;

import it.sephiroth.android.library.imagezoom.ImageViewTouch;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;

public class ImageDetectionActivity extends Activity
{
	private final String TAG = "TAG_LOG";
	private File mCascadeFile;
	private CascadeClassifier mCascadeClassifier;
	private Rect[] objsArray; // Array of rectangles that bound all detected objects
	private Rect[] facesArray; // Array of rectangles that bound all detected faces
	private Rect[] eyesArray; // Array of rectangles that bound all detected eyes
	private Rect[] nosesArray; // Array of rectangles that bound all detected noses
	private Rect[] mouthsArray; // Array of rectangles that bound all detected mouths
	private Mat image;
	private Mat mRGBA;
	private String imagePath;
	private boolean[] rectChecked;
	private ImageViewTouch image_view_touch;
	private Button detect_face;
	private Button detect_eyes;
	private Button detect_nose;
	private Button detect_mouth;
	
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
		setContentView(R.layout.activity_image_detection);
		Intent intent = getIntent();
		imagePath = intent.getStringExtra("ImagePath");
		image_view_touch = (ImageViewTouch)findViewById(R.id.image_view_touch);
		// Load image into ImageViewTouch
		loadImage();
		detect_face = (Button)findViewById(R.id.detect_face);
		detect_face.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				
				Log.i(TAG, "Trying to load OpenCV library");
				
				if (!OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_5, getBaseContext(), mOpenCVCallBack))
				{
					Log.e(TAG, "Cannot connect to OpenCV Manager");
				}
			}
		});
		detect_eyes = (Button)findViewById(R.id.detect_eyes);
		detect_eyes.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				new Thread(new Runnable()
				{
					public void run()
					{
						detectEyes();
						image_view_touch.post(new Runnable()
						{
							public void run()
							{
								image_view_touch.setImageBitmap(drawImage(), false, null, 5.0f);
								detect_eyes.setEnabled(false);
							}
						});
					}
				}).start();
			}
		});
		detect_nose = (Button)findViewById(R.id.detect_nose);
		detect_nose.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				new Thread(new Runnable()
				{
					public void run()
					{
						detectNoses();
						image_view_touch.post(new Runnable()
						{
							public void run()
							{
								image_view_touch.setImageBitmap(drawImage(), false, null, 5.0f);
								detect_nose.setEnabled(false);
							}
						});
					}
				}).start();
			}
		});
		detect_mouth = (Button)findViewById(R.id.detect_mouth);
		detect_mouth.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				new Thread(new Runnable()
				{
					public void run()
					{
						detectMouths();
						image_view_touch.post(new Runnable()
						{
							public void run()
							{
								image_view_touch.setImageBitmap(drawImage(), false, null, 5.0f);
								detect_mouth.setEnabled(false);
							}
						});
					}
				}).start();
			}
		});
	}
	
	private void loadImage()
	{
		Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
		image_view_touch.setImageBitmap(bitmap, true, null, 5.0f);
	}

	private void loadCascade(int casfile)
	{
		try
		{
			InputStream is = getBaseContext().getResources().openRawResource(casfile);
			File cascadeDir = getBaseContext().getDir("cascade", Context.MODE_PRIVATE);
			mCascadeFile = new File(cascadeDir, "haarcascade_object.xml");
			FileOutputStream os = new FileOutputStream(mCascadeFile);
			
			byte[] buffer = new byte[4096];
			int bytesRead;
			while ((bytesRead = is.read(buffer)) != -1)
			{
				os.write(buffer, 0, bytesRead);
			}
			
			is.close();
			os.close();
			
			mCascadeClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());
			if (mCascadeClassifier.empty())
			{
				Log.e(TAG, "Failed to load cascade classifier");
				mCascadeClassifier = null;
			}
			else
			{
				Log.i(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath());
			}
			cascadeDir.delete();
		}
		catch (IOException e)
		{
			e.printStackTrace();
			Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
		}
	}
	
	private void detectObject(Mat img, float minSize)
	{
		MatOfRect faces = new MatOfRect();
		objsArray = null;
		int mAbsoluteFaceSize = 0;
		int height = img.rows();
		float mRelativeFaceSize = minSize;
		if (Math.round(height * mRelativeFaceSize) <= 0)
		{
			mAbsoluteFaceSize = Math.round(height * mRelativeFaceSize);
		}
		if (mCascadeClassifier != null)
		{
			mCascadeClassifier.detectMultiScale(img, faces, 1.1, 2,
					2, // TODO: objdetect.CV_HAAR_SCALE_IMAGE
					new Size(mAbsoluteFaceSize, mAbsoluteFaceSize),
					new Size());
		}
		objsArray = faces.toArray();
	}
	
	public void detectFaces()
	{
		loadCascade(R.raw.haarcascade_frontalface_alt);
		detectObject(image, 0.05f);
		facesArray = objsArray;
	}
	
	public Bitmap drawImage()
	{
		try
		{
			mRGBA = Highgui.imread(imagePath);
			Imgproc.cvtColor(mRGBA, mRGBA, Imgproc.COLOR_BGR2RGBA);
			int thickness = mRGBA.cols() > mRGBA.rows() ? (mRGBA.rows() / 320 + 1)
					: (mRGBA.cols() / 320 + 1);
			if (facesArray != null)
			{
				for (int i = 0; i < facesArray.length; i++)
				{
					// RGBA
					Scalar tmp = new Scalar(0, 255, 0, 255);
					if (rectChecked[i] == true)
					{
						tmp = new Scalar(255, 0, 0, 255);
					}
					// tl = top-left, br = bottom-right
					Core.rectangle(mRGBA, facesArray[i].tl(), facesArray[i].br(), tmp, thickness);
				}
			}
			if (eyesArray != null)
			{
				for (int i = 0; i < eyesArray.length; i++)
				{
					if (eyesArray[i] != null)
					{
						Core.rectangle(mRGBA, eyesArray[i].tl(), eyesArray[i].br(), new Scalar(0, 255, 0, 255), thickness);
					}
				}
			}
			if (nosesArray != null)
			{
				for (int i = 0; i < nosesArray.length; i++)
				{
					if (nosesArray[i] != null)
					{
						Core.rectangle(mRGBA, nosesArray[i].tl(), nosesArray[i].br(), new Scalar(0, 255, 0, 255), thickness);
					}
				}
			}
			if (mouthsArray != null)
			{
				for (int i = 0; i < mouthsArray.length; i++)
				{
					if (mouthsArray[i] != null)
					{
						Core.rectangle(mRGBA, mouthsArray[i].tl(), mouthsArray[i].br(), new Scalar(0, 255, 0, 255), thickness);
					}
				}
			}
			
			int mx = mRGBA.cols() > mRGBA.rows() ? mRGBA.cols() : mRGBA.rows();
			float rd = 1200f / (float) mx;
			
			// Resize the image if it's too large
			if (rd < 1f)
			{
				Mat tmpMat = new Mat(new Size(mRGBA.cols() * rd, mRGBA.rows() * rd), mRGBA.type());
				Imgproc.resize(mRGBA, tmpMat, new Size(mRGBA.cols() * rd, mRGBA.rows() * rd));
				
				Bitmap bmp = Bitmap.createBitmap(tmpMat.cols(), tmpMat.rows(), Bitmap.Config.ARGB_8888);
				Utils.matToBitmap(tmpMat, bmp);
				return bmp;
			}
			else
			{
				Bitmap bmp = Bitmap.createBitmap(mRGBA.cols(), mRGBA.rows(), Bitmap.Config.ARGB_8888);
				Utils.matToBitmap(mRGBA, bmp);
				return bmp;
			}
		}
		catch (Exception ex)
		{
			return null;
		}
	}
	
	private BaseLoaderCallback mOpenCVCallBack = new BaseLoaderCallback(this)
	{
		@Override
		public void onManagerConnected(int status)
		{
			switch (status)
			{
				case LoaderCallbackInterface.SUCCESS:
				{
					if (image == null)
					{
						image = Highgui.imread(imagePath, 0);
						facesArray = null;
						detectFaces();
						rectChecked = new boolean[facesArray.length];
						for (int i = 0; i < facesArray.length; i++)
						{
							rectChecked[i] = false;
						}
					}
					image_view_touch.setImageBitmap(drawImage(), true, null, 5.0f);
					if (facesArray != null)
					{
						detect_eyes.setEnabled(true);
						detect_nose.setEnabled(true);
						detect_mouth.setEnabled(true);
						detect_face.setEnabled(false);
					}
					break;
				}
				default:
				{
					super.onManagerConnected(status);
					break;
				}
			}
		}
	};
	
	public Rect crop(Rect rect, float fleft, float ftop, float fright, float fbottom)
	{
		double left = rect.tl().x + (rect.br().x - rect.tl().x) * fleft;
		double top = rect.tl().y + (rect.br().y - rect.tl().y) * ftop;
		double right = rect.br().x - (rect.br().x - rect.tl().x) * fright;
		double bottom = rect.br().y - (rect.br().y - rect.tl().y) * fbottom;
		Point p1 = new Point(left, top);
		Point p2 = new Point(right, bottom);
		Rect result = new Rect(p1, p2);
		return result;
	}
	
	public void detectEyes()
	{
		eyesArray = new Rect[20];
		int count = 0;
		for (int i = 0; i < eyesArray.length; i++)
		{
			eyesArray[i] = null;
		}
		loadCascade(R.raw.haarcascade_eye);
		for (int i = 0; i < facesArray.length; i++)
		{
			Rect tmpRect = crop(facesArray[i], 0f, 0f, 0f, 0.5f);
			Mat cropped = new Mat(image, tmpRect);
			detectObject(cropped, 0.1f);
			if (objsArray != null)
			for (int j = 0; j < objsArray.length; j++)
			{
				double left = tmpRect.tl().x + objsArray[j].tl().x;
				double top = tmpRect.tl().y + objsArray[j].tl().y;
				double bottom = tmpRect.tl().y + objsArray[j].br().y;
				double right = tmpRect.tl().x + objsArray[j].br().x;
				Point p1 = new Point(left, top);
				Point p2 = new Point(right, bottom);
				eyesArray[count] = new Rect(p1, p2);
				count++;
			}
		}
	}

	public void detectNoses()
	{
		nosesArray = new Rect[20];
		int count = 0;
		for (int i = 0; i < nosesArray.length; i++)
		{
			nosesArray[i] = null;
		}
		loadCascade(R.raw.haarcascade_mcs_nose);
		for (int i = 0; i < facesArray.length; i++)
		{
			Rect tmpRect = crop(facesArray[i], 0.2f, 0.2f, 0.2f, 0.2f);
			Mat cropped = new Mat(image, tmpRect);
			detectObject(cropped, 0.25f);
			if (objsArray != null)
			{
				for (int j = 0; j < objsArray.length; j++)
				{
					double left = tmpRect.tl().x + objsArray[j].tl().x;
					double top = tmpRect.tl().y + objsArray[j].tl().y;
					double bottom = tmpRect.tl().y + objsArray[j].br().y;
					double right = tmpRect.tl().x + objsArray[j].br().x;
					Point p1 = new Point(left, top);
					Point p2 = new Point(right, bottom);
					nosesArray[count] = new Rect(p1, p2);
					count++;
				}
			}
		}
	}

	public void detectMouths()
	{
		mouthsArray = new Rect[20];
		int count = 0;
		for (int i = 0; i < mouthsArray.length; i++)
		{
			mouthsArray[i] = null;
		}
		loadCascade(R.raw.haarcascade_mcs_mouth);
		for (int i = 0; i < facesArray.length; i++)
		{
			Rect tmpRect = crop(facesArray[i], 0.3f, 0.7f, 0.3f, 0f);
			Mat cropped = new Mat(image, tmpRect);
			detectObject(cropped, 0.35f);
			if (objsArray != null)
			{
				for (int j = 0; j < objsArray.length; j++)
				{
					double left = tmpRect.tl().x + objsArray[j].tl().x;
					double top = tmpRect.tl().y + objsArray[j].tl().y;
					double bottom = tmpRect.tl().y + objsArray[j].br().y;
					double right = tmpRect.tl().x + objsArray[j].br().x;
					Point p1 = new Point(left, top);
					Point p2 = new Point(right, bottom);
					mouthsArray[count] = new Rect(p1, p2);
					count++;
				}
			}
		}
	}
}